﻿using Microsoft.AnalysisServices.AdomdClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Xml;

namespace SQX.DataModel
{
    public class SyntaxMarkup
    {
        private const string MDXFunctionRef = @"SELECT FUNCTION_NAME, [DESCRIPTION], PARAMETER_LIST, INTERFACE_NAME FROM $system.MDSCHEMA_FUNCTIONS";
        private List<LanguageObject> languageObjects;
        private List<Tag> tags;

        private struct Tag
        {
            public TextPointer startPosition;
            public TextPointer endPosition;
            public LanguageObject tagObject;
        }

        private void fillReserverWordList(XmlDocument doc)
        {
            var reservedNodes = doc.GetElementsByTagName("word");
            for (int i = 0; i < reservedNodes.Count; ++i)
            {
                var word = new LanguageReserved(reservedNodes[i].InnerText);
                languageObjects.Add(word);
            }
        }

        private void fillCommentList(XmlDocument doc)
        {
            var commentNodes = doc.GetElementsByTagName("comment");
            for (int i = 0; i < commentNodes.Count;++i)
            {
                var comment = new LanguageComment(commentNodes[i].InnerText);
                languageObjects.Add(comment);
            }
        }
        
        public SyntaxMarkup(Service.SourceType Source)
        {
            languageObjects = new List<LanguageObject>();
            tags = new List<Tag>();
            XmlDocument doc = new XmlDocument();

            switch (Source)
            {
                case Service.SourceType.SourceOLAP:
                    doc.LoadXml(Properties.Resources.mdxSyntax);
                    fillReserverWordList(doc);
                    fillCommentList(doc);
                    break;
                case Service.SourceType.SourceSQL:
                    doc.LoadXml(Properties.Resources.sqlSyntax);
                    fillReserverWordList(doc);
                    fillCommentList(doc);
                    break;
            }
        }

        public void SetMDXFuncs(AdomdConnection connection)
        {
            connection.Open();
            AdomdCommand cmd = new AdomdCommand(MDXFunctionRef, connection);
            AdomdDataReader rdr = cmd.ExecuteReader();
            foreach (IDataRecord rw in rdr.Cast<IDataRecord>())
                {
                    var func = new LanguageFunction(rw[0] != null ? rw[0].ToString() : "",
                                                    rw[1] != null ? rw[1].ToString() : "",
                                                    rw[2] != null ? rw[2].ToString() : "",
                                                    rw[3] != null ? rw[3].ToString() : ""); 
                 languageObjects.Add(func);
                }
            connection.Close();
        }

        public void SetSQLFunc(SqlConnection connection)
        {

        }

        public void TryColorSyntax(RichTextBox box, TextChangedEventHandler TextChanged, TextRange scanned)
        {
            //Unregister to prevent stackoverflow
            box.TextChanged -= TextChanged;

            TextPointer navigator = scanned.Start;
            while (navigator.CompareTo(scanned.End) < 0)
            {
                TextPointerContext context = navigator.GetPointerContext(LogicalDirection.Backward);
                if (context == TextPointerContext.ElementStart && navigator.Parent is Run)
                {
                    CheckWordsInRun((Run)navigator.Parent);
                }
                navigator = navigator.GetNextContextPosition(LogicalDirection.Forward);
            }

            tags.ForEach(tg =>
                {
                    TextRange range = new TextRange(tg.startPosition, tg.endPosition);
                    range.ApplyPropertyValue(TextElement.ForegroundProperty, tg.tagObject.ObjectTextColor);
                    range.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.SemiBold);
                });

            tags.Clear();

                // register to allow coloring again
                box.TextChanged += TextChanged;

        }

        /// <summary>
        /// Finds words that are ready for coloring
        /// </summary>
        /// <param name="run">XAML <b>Run</b> tag</param>
        private void CheckWordsInRun(Run run)
        {
            if (run.Text != "")
            {
                string source = run.Text.Replace('\t', ' ').Replace('\r', ' ').Insert(run.Text.Length, " ").ToLower();
                languageObjects.AsParallel().ForAll(rw =>
                    {
                        Tag t = new Tag();
                        int i = 0;
                        int index = 0;
                        lock (run)
                        {
                            while (i < source.Length & index != -1)
                            {
                                index = source.IndexOf(rw.ObjectName.ToLower(), i);
                                if (rw.Description == "$Reserved$")
                                {
                                    if (index != -1 && (source.Length == rw.ObjectName.Length || Char.IsWhiteSpace(source[index + rw.ObjectName.Length])))
                                    {
                                        if (index == 0 || (source.Length == rw.ObjectName.Length || Char.IsWhiteSpace(source[index - 1])))
                                        {
                                            t.startPosition = run.ContentStart.GetPositionAtOffset(index, LogicalDirection.Forward);
                                            t.endPosition = run.ContentStart.GetPositionAtOffset(index + rw.ObjectName.Length, LogicalDirection.Backward);
                                            t.tagObject = rw;
                                        }
                                    }
                                }
                                else if (rw.Description == "$Comment$")
                                {
                                    if (index != -1 && source.Substring(0, 2) == rw.ObjectName)
                                    {
                                        if (index == 0 || (source.Length == rw.ObjectName.Length || Char.IsWhiteSpace(source[index - 1])))
                                        {
                                            t.startPosition = run.ContentStart;
                                            t.endPosition = run.ContentEnd;
                                            t.tagObject = rw;
                                        }
                                    }

                                }
                                else
                                {
                                    if (index != -1 && source.Substring(index, rw.ObjectName.Length) == rw.ObjectName.ToLower())
                                    {
                                        if (index == 0 || (source.Length == rw.ObjectName.Length || Char.IsWhiteSpace(source[index - 1])))
                                        {
                                            t.startPosition = run.ContentStart.GetPositionAtOffset(index, LogicalDirection.Forward);
                                            t.endPosition = run.ContentStart.GetPositionAtOffset(index + rw.ObjectName.Length, LogicalDirection.Backward);
                                            t.tagObject = rw;
                                        }
                                    }
                                }

                                lock (tags)
                                {
                                    if (t.tagObject != null) tags.Add(t);
                                }
                                i = index + 1;

                            }
                        }

                    });
            }
        }

        public List<LanguageObject> LanguageObjects { get { return languageObjects; } }

    }

    // function description
    public class LanguageFunction : LanguageObject
    {
        public LanguageFunction(string fName, string Desc, string Params, string InterfaceName) : base(fName, Desc)
        {
            this.Parameters = Params;
            this.InterfaceName = InterfaceName;
            base.objectTextColor = Service.GetSyntaxColor(Service.SyntaxTypes.SyntaxFunction);
        }

        public string Parameters { get; set; }
        public string InterfaceName { get; set; } 
    }

    public class LanguageReserved : LanguageObject
    {
        public LanguageReserved(string wName) : base(wName, "$Reserved$")
        {
            this.ObjectName = wName;
            base.objectTextColor = Service.GetSyntaxColor(Service.SyntaxTypes.SyntaxReserved);
        }
    }

    public class LanguageComment : LanguageObject
    {
        public LanguageComment(string Name) : base(Name,"$Comment$")
        {
            base.objectTextColor = Service.GetSyntaxColor(Service.SyntaxTypes.SyntaxComment);
        }
    }

    public class DatabaseElement : LanguageObject
    {
        public DatabaseElement(string Name, string Desc) : base(Name, Desc)
        {
            base.objectTextColor = new SolidColorBrush(Color.FromRgb(0, 0, 0));
        }
    }

    public abstract class LanguageObject
    {
        protected SolidColorBrush objectTextColor;

        public LanguageObject(string Name, string Desc)
        {
            this.ObjectName = Name;
            this.Description = Desc;
        }

        public string ObjectName { get; set; }
        public string Description { get; set; }
        public SolidColorBrush ObjectTextColor { get { return objectTextColor; } } 
    }
}
